/*
 * Created on Nov 26, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.hermeneute.fsa;

import java.io.IOException;
import java.io.Reader;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author mauceri
 * 
 * Preferences - Java - Code Style - Code Templates
 */

public abstract class FSTAbstractReader extends Reader {

	public class EpsilonMarker {
		public int start;
		public String replacePattern = null;
		public int end;
	}
	class ParseStack {
		class Context {
			public int mark;
			public int position;
			public int state;
			public char type;
		}
		Context[] ctx;
		int depth;
		
		ParseStack(int size) {
			ctx = new Context[size];
			depth = -1;
		}
		public void init () {
			depth = -1;
			for (int i = 0; ctx[i] != null; i++) {
				ctx[i] = null;
			}
		}

		public void push(int state,int pos, char type) throws Exception{
			if (depth >= ctx.length) {
				throw new Exception("Parse stack overflow");
			} else {
				depth++;
				ctx[depth] = new Context();
				ctx[depth].state = state;
				ctx[depth].position = pos;
				ctx[depth].type = type;
			}
		}
		
		public void pop() throws Exception {
			if (depth < 0) {
				throw new Exception("Parse stack is empty");
			} else {
				ctx[depth] = null;
				depth--;
			}			
		}
		
		public int getPosition () throws Exception {
			if (depth < 0 || depth >= ctx.length) {
				throw new Exception("Parse stack is out of bound");
			} else {
				return ctx[depth].position;
			}						
		}

		public int getState () throws Exception {
			if (depth < 0 || depth >= ctx.length) {
				throw new Exception("Parse stack is out of bound");
			} else {
				return ctx[depth].state;
			}						
		}

		public char getType () throws Exception {
			if (depth < 0 || depth >= ctx.length) {
				throw new Exception("Parse stack is out of bound");
			} else {
				return ctx[depth].type;
			}						
		}
		
		public boolean isEmpty () {
			return depth < 0;
		}

		void stackAnalysis() {
			int MAMax = 0;
			int markPosition = 0;
			
			//System.out.print("Start:");
			for (int i = 0; ctx[i] != null; i++) {
				//System.out.print(ctx[i].type);
				if (ctx[i].type == '(') {
					EpsilonMarker em = new EpsilonMarker();
					em.start = ctx[i].position;
					marks.add(em);
					ctx[markPosition++].mark = MAMax;
					//System.out.print(buffer[ctx[i].position]+" "+MAMax);
					MAMax++;					
				} else if (ctx[i].type == '.') {
					markPosition--;
					EpsilonMarker em = 
						(EpsilonMarker)marks.elementAt(ctx[markPosition].mark);
					em.end = ctx[i].position;
					//System.out.print(buffer[ctx[i].position-1]+" "+ctx[markPosition].mark);
				}
			}
			//System.out.println("End:");
		}
		
		public void finish () {
			stackAnalysis();
		}
	}

	//public static int MAX_REC_DEPTH = 5000;
	public static int MAX_REC_DEPTH = 100000;
	ParseStack stack = new ParseStack(MAX_REC_DEPTH);

	private Stack currentMark = new Stack();

	private String epsilonProduction = null;

	protected String lastEpsilonProduction = null;

	private char outputBuffer[] = null;

	private char overflowBuffer[] = null;

	private int outputBufferStart = 0;

	private int outputBufferEnd = 0;

	private boolean match = false;

	private boolean eof = false;

	protected char[] buffer = null;

	protected int endOfBuffer = 0;

	protected int currentPosition = 0;

	private int newCurrentPosition = 0;

	private int alreadyDone = 0;

	private int beginMark = 0;

	private int endMark = 0;

	private int currentState = 0;

	private int lastPos = 0;

	protected Vector marks = new Vector();
	
	protected int nc = 0;

	protected int nr = 0;

	private Reader in = null;

	private FSTSequentialTransducer st = null;

	public static final int FILTER = 1;

	public static final int LISTER = 2;

	private int type = FILTER;

	public FSTAbstractReader(FSTSequentialTransducer st, Reader in) {
		super();
		this.in = in;
		this.st = st;
		this.type = FILTER;
		this.nc = 0;
	}

	public FSTAbstractReader(FSTSequentialTransducer st, Reader in, int type) {
		super();
		this.in = in;
		this.st = st;
		this.type = type;
		this.nc = 0;
	}

	public void close() throws IOException {
		in.close();
	}

	public int read(char[] arg0, int arg1, int arg2) throws IOException {
		outputBuffer = arg0;
		outputBufferStart = arg1;
		outputBufferEnd = arg1 + arg2;
		try {
			if (arg2 < alreadyDone) {
				for (int i = 0; i < arg2; i++) {
					outputBuffer[i + arg1] = overflowBuffer[i];
				}
				for (int i = arg2; i < alreadyDone; i++) {
					overflowBuffer[i - arg2] = overflowBuffer[i];
				}
				alreadyDone = alreadyDone - arg2;
				return arg2;
			}

			if (alreadyDone > 0) {
				for (int i = 0; i < alreadyDone; i++) {
					outputBuffer[i + arg1] = overflowBuffer[i];
				}
			}
			currentPosition = 0;
			if (arg2 > endOfBuffer + alreadyDone) {
				if (fillBuffer(arg2 - endOfBuffer - alreadyDone) == -1
						&& endOfBuffer == 0) {
					if (alreadyDone == 0) {
						eof = true;
						return -1;
					} else {
						int ret = alreadyDone;
						alreadyDone = 0;
						return ret;
					}
				}
			} else {
				//System.out.println("Enough in buffer");
			}

			int outn = arg1 + alreadyDone;
			alreadyDone = 0;
			eof = false;
			currentPosition = 0;
			while (outn < outputBufferEnd && !eof) {
				init();
				marks.clear();
				beginMark = 0;
				int initialPosition = currentPosition;
				if (parse(0)) {
					outn = evalTodo(initialPosition, outn);
				} else {
					currentPosition = initialPosition;
					if (currentPosition < endOfBuffer) {
						if (type == FILTER) {
							writeOut(buffer[currentPosition], outn);
							outn++;
						}
						currentPosition++;
						eof = false;
					} else if (fillBuffer(endOfBuffer) != -1) {
						//System.out.println("Refuel");
						if (type == FILTER) {
							writeOut(buffer[currentPosition], outn);
							outn++;
						}
						currentPosition++;
						eof = false;
					} else {
						eof = true;
					}
				}
			}
			if (outn == 0)
				return -1;
			if (outn > outputBufferEnd) {
				alreadyDone = outn - outputBufferEnd;
				if (endOfBuffer > currentPosition) {
					for (int i = currentPosition; i < endOfBuffer; i++) {
						buffer[i - currentPosition] = buffer[i];
					}
					endOfBuffer = endOfBuffer - currentPosition;
				} else {
					endOfBuffer = 0;
				}
				return arg2;
			} else if (outn == outputBufferEnd) {
				if (endOfBuffer > currentPosition) {
					for (int i = currentPosition; i < endOfBuffer; i++) {
						buffer[i - currentPosition] = buffer[i];
					}
					endOfBuffer = endOfBuffer - currentPosition;
				} else {
					endOfBuffer = 0;
				}
				alreadyDone = 0;
				return arg2;
			} else {
				if (endOfBuffer > currentPosition) {
					for (int i = currentPosition; i < endOfBuffer; i++) {
						buffer[i - currentPosition] = buffer[i];
					}
					endOfBuffer = endOfBuffer - currentPosition;
				} else {
					endOfBuffer = 0;
				}
				alreadyDone = 0;
				//PrintOutput(arg1,outn-arg1);

				return outn - arg1 - alreadyDone;
			}
		} catch (IOException x) {
			x.printStackTrace();
			throw x;
		} catch (Exception x) {
			x.printStackTrace();
			throw new IOException(x.getMessage());
		}
	}

	protected void PrintOutput(int from, int to) throws Exception {
		for (int i = from; i < to; i++) {
			System.out.print(outputBuffer[i]);
		}
		System.out.println();
	}


	protected void writeOut(char val, int pos) throws Exception {
		if (pos < outputBufferEnd) {
			outputBuffer[pos] = val;
		} else {
			if (overflowBuffer == null) {
				overflowBuffer = new char[outputBuffer.length];
			} else if (pos - outputBufferEnd >= overflowBuffer.length) {
				char[] tmp = new char[overflowBuffer.length * 2];
				for (int i = 0; i < overflowBuffer.length; i++) {
					tmp[i] = overflowBuffer[i];
				}
				overflowBuffer = tmp;
			}
			overflowBuffer[pos - outputBufferEnd] = val;
		}
	}

	protected String getReplacePattern(String todo) {
		boolean escapec = false;
		int pos = -1;
		String replacePattern;

		for (int i = 0; i < todo.length(); i++) {
			if (todo.charAt(i) == '\\') {
				escapec = true;
				continue;
			} else if (escapec == true) {
				escapec = false;
				continue;
			} else if (todo.charAt(i) == ',') {
				pos = i;
				replacePattern = todo.substring(0,pos);
				return replacePattern;
			}
		}
		return todo;
	}

	Integer getPosition (char [] replacePattern, int offset) {
		int j = offset;
		while(j < replacePattern.length && 
				Character.isDigit(replacePattern[j])) {
			j++;
		}
		return Integer.valueOf(new String(replacePattern,offset,j-offset));
	}
	
	protected int expandReplacePattern(char [] replacePattern,char [] tmpBuffer) {
		boolean escapec = false;
		int posTmpBuffer = 0;
		
		int i = 0;
		while (i < replacePattern.length) {
			if (i+1 < replacePattern.length &&
					replacePattern[i] == '\\' && 
						replacePattern[i+1] == '$') {
				tmpBuffer[posTmpBuffer] = replacePattern[i+1];
				posTmpBuffer++;
				i = i + 2;
			} else if (i+1 < replacePattern.length &&
						replacePattern[i] == '$' && 
							Character.isDigit(replacePattern[i+1])) {
				int markerPos = getPosition(replacePattern,i+1).intValue();
				EpsilonMarker marker = 
					(EpsilonMarker) marks.elementAt(markerPos-1);
				for (int j = marker.start; j < marker.end; j++) {
					tmpBuffer[posTmpBuffer++] = buffer[j];
				}
				i = i + Integer.toString(markerPos).length()+1;
			} else {
				tmpBuffer[posTmpBuffer++] = replacePattern[i++];
			}
		}
		return posTmpBuffer;
	}
	
	abstract protected int evalTodo(int initialPosIn, int initialPositionOut) throws Exception ;

	void init() {
		currentState = st.start;
		lastEpsilonProduction = "";
		epsilonProduction = "";
		lastPos = 0;
	}

	private int fillBuffer(int n) throws Exception {
		nr = 0;
		//System.out.println("Trying to get "+n+" chars");
		if (buffer == null) {
			buffer = new char[n];
			nr = in.read(buffer, 0, n);
			nc = nr;
		} else if (buffer.length - endOfBuffer >= n) {
			nr = in.read(buffer, endOfBuffer, n);
			nc += nr;
		} else {
			char tmp[] = new char[endOfBuffer + n];
			for (int i = 0; i < endOfBuffer; i++) {
				tmp[i] = buffer[i];
			}
			buffer = tmp;
			nr = in.read(buffer, endOfBuffer, n);
			nc += nr;
		}
		for (int i = 0; i < nr; i++) {
			if (buffer[endOfBuffer + i] == 160) {
				buffer[endOfBuffer + i] = ' ';
			}
		}
		if (nr == -1 || nr == 0) {
			return -1;
		} else {
			currentPosition = endOfBuffer;
			endOfBuffer += nr;
			return endOfBuffer;
		}
	}

	boolean getNext() throws Exception {
		FSTCompiledTransition res = null;
		if (currentPosition >= endOfBuffer) {
			if (fillBuffer(endOfBuffer) == -1) {
				eof = true;
				return false;
			}
		}
		res = st.nextState(currentState, buffer[currentPosition]);
		if (res == null) {
			return false;
		} else {
			currentPosition++;
			currentState = res.state;
			return true;
		}
	}

	boolean tryLeftTag(int recdepth) throws Exception {
		FSTCompiledTransition res = st.nextEpsilonState(currentState);

		if (res == null) {
			return false;
		} else if (res.output == '(') {
			currentState = res.state;
			beginMark = currentPosition;
			return parse(recdepth + 1);
		} else {
			return false;
		}
	}

	boolean hasLeftTag() {
		FSTCompiledTransition res = st.nextEpsilonState(currentState);

		if (res == null) {
			return false;
		} else if (res.output == '(') {
			return true;
		} else {
			return false;
		}
	}

	boolean tryRightTag(int recdepth) throws Exception {
		FSTCompiledTransition res = st.nextEpsilonState(currentState);
		if (res == null) {
			return false;
		} else if (res.output == ')') {
			currentState = res.state;
			EpsilonMarker tmpem = new EpsilonMarker();
			tmpem.start = beginMark;
			tmpem.end = currentPosition;
			marks.add(tmpem);
			checkFinalState();
			if (parse(recdepth + 1)) {
				return true;
			} else {
				marks.remove(tmpem);
				return false;
			}
		} else {
			currentState = res.state;
			return false;
		}
	}

	boolean hasRightTag() {
		FSTCompiledTransition res = st.nextEpsilonState(currentState);

		if (res == null) {
			return false;
		} else if (res.output == ')') {
			return true;
		} else {
			return false;
		}
	}

	boolean tryEpsilon() throws Exception {
		int initialState = currentState;
		epsilonProduction = "";
		FSTCompiledTransition res = st.nextEpsilonState(currentState);
		while (res != null) {
			epsilonProduction += res.output;
			currentState = res.state;
			if (checkFinalState())
				return true;
			res = st.nextEpsilonState(currentState);
		}
		currentState = initialState;
		return false;
	}

	boolean hasEpsilon() {
		FSTCompiledTransition res = st.nextEpsilonState(currentState);
		if (res == null) {
			return false;
		} else {
			return true;
		}
	}

	boolean checkFinalState() {
		if (st.finalStates.contains(new Integer(currentState))) {
			match = true;
			lastPos = currentPosition;
			lastEpsilonProduction = epsilonProduction;
			return true;
		} else {
			return false;
		}
	}

	boolean parseRight(int recdepth) throws Exception {
		if (getNext()) {
			return parse(recdepth + 1);
		} else {
			return false;
		}
	}
	
	
	boolean parse(int recdepth) throws Exception {
		boolean res = false;
		match = false;
		stack.init();
		int initialPosition = currentPosition;
		int initialState = currentState;
		boolean hasNext = true;
		while (hasNext) {
			hasNext = false;
			if (hasLeftTag()) {
				//System.out.print("(");
				stack.push(currentState,currentPosition,'(');
				FSTCompiledTransition ct = st.nextEpsilonState(currentState);
				currentState = ct.state;
				hasNext = true;
			}
			else
			if (hasRightTag()) {
				stack.push(currentState,currentPosition,')');
			}
			else
			if (hasEpsilon()&&!hasRightTag()&&!hasLeftTag()) {
				stack.push(currentState,currentPosition,'|');
			}
			
			if (getNext()) {
				hasNext = true;
			}
				
			while (!hasNext && !stack.isEmpty()){
				currentState = stack.getState();
				currentPosition = stack.getPosition();
				if (stack.getType() == '.') {
					stack.pop();
				}
				else if (stack.getType() == ')') {
					stack.pop();
					stack.push(currentState,currentPosition,'.');
					FSTCompiledTransition ct = st.nextEpsilonState(currentState);
					currentState = ct.state;
					hasNext = true;
				} else if (stack.getType() == '(') {
					hasNext = getNext();
					stack.pop();
				} else if (stack.getType() == '|') {
					match = tryEpsilon();
					stack.finish();
					return true;
				}
			}
		}
		currentPosition = initialPosition;
		return false;
	}
}